package com.diploma.project.aiqiyi.common.config.feign;

import com.diploma.project.aiqiyi.common.config.feign.event.FeignListener;
import com.diploma.project.aiqiyi.common.config.feign.event.FeignListenerInterface;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
import feign.Target;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Optional;

import static feign.Util.checkNotNull;

/**
 * @author lisheng
 * @description
 * @date 2018/12/20 9:47
 * @version:
 */
public class FeignInvocationHandlerFactory implements InvocationHandlerFactory, ApplicationContextAware {

    private ApplicationContext applicationContext;
    private FeignContext feignContext;

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
        return new FeignInvocationHandler(target, dispatch);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.feignContext = this.applicationContext.getBean(FeignContext.class);
    }

    class FeignInvocationHandler implements InvocationHandler {
        private final Target target;
        private final Map<Method, MethodHandler> dispatch;

        FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
            this.target = checkNotNull(target, "target");
            this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("equals".equals(method.getName())) {
                try {
                    Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                    return equals(otherHandler);
                } catch (IllegalArgumentException e) {
                    return false;
                }
            } else if ("hashCode".equals(method.getName())) {
                return hashCode();
            } else if ("toString".equals(method.getName())) {
                return toString();
            }

            FeignListenerInterface listenerInterface = getFeignListenerInterface(method);
            MethodMetadata methodMetadata = getMethodMetadata(method);
            try {
                if (listenerInterface != null) {
                    listenerInterface.executeBefore(method, args, methodMetadata);
                }
                Object invoke = dispatch.get(method).invoke(args);

                if (listenerInterface != null) {
                    Object afterObject = listenerInterface.executeAfter(method, invoke, methodMetadata);
                    if (afterObject != null) return afterObject;
                }
                return invoke;
            } catch (Throwable throwable) {
                if (listenerInterface != null) {
                    Object failureObject = listenerInterface.executeFailure(method, throwable, methodMetadata);
                    if (failureObject != null) return failureObject;
                }
                throw throwable;
            }
        }

        protected FeignListenerInterface getFeignListenerInterface(Method method) {
            FeignListener annotation = method.getDeclaringClass().getAnnotation(FeignListener.class);
            FeignListener methodAnnotation = method.getAnnotation(FeignListener.class);
            if (methodAnnotation != null) annotation = methodAnnotation;
            if (annotation == null) return null;

            FeignContext feignContext = FeignInvocationHandlerFactory.this.feignContext;
            if (feignContext == null) return null;

            Map<String, ? extends FeignListenerInterface> instances = feignContext.getInstances(target.name(), annotation.value());
            if (instances == null || instances.isEmpty()) return null;

            String methodName = annotation.methodName();
            if (StringUtils.hasLength(methodName)) {
                FeignListenerInterface listener = instances.get(methodName);
                if (listener != null) return listener;
            }

            Optional<? extends FeignListenerInterface> first = instances.values().stream().findFirst();
            return first.orElse(null);
        }

        protected MethodMetadata getMethodMetadata(Method method) {
            MethodHandler methodHandler = dispatch.get(method);
            Field field = ReflectionUtils.findField(methodHandler.getClass(), "metadata", MethodMetadata.class);
            MethodMetadata methodMetadata = null;
            if (field != null) {
                ReflectionUtils.makeAccessible(field);
                methodMetadata = (MethodMetadata) ReflectionUtils.getField(field, methodHandler);
            }
            return methodMetadata;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof FeignInvocationHandler) {
                FeignInvocationHandler other = (FeignInvocationHandler) obj;
                return target.equals(other.target);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return target.hashCode();
        }

        @Override
        public String toString() {
            return target.toString();
        }
    }
}
